#pragma once
#include<elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <iostream>
#include<vector>
#include <memory>
#include "logger.hpp" 
//es的功能之一可以将数据分段保存的存储系统
//这个文件的作用即将es的增删查改功能进行二次封装以便于更好的在本项目使用
//值得注意的点是，在es7.0以后，索引名字与类型共同标识一个索引名字
//(即一个索引名字只能有一个类型，且只要同时创建了一个索引名字和类型，类型便与索引名字绑定，即使将索引删除完)
//且一个索引名字可以存在多个索引，通过索引的id来唯一表示
namespace zzy_mims
{
bool Serialize(Json::Value&  val,std::string& dst)//将Json::Value序列化为字符串
{
    Json::StreamWriterBuilder swb;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream ss;
    if(sw->write(val,&ss)!=0)
    {
         std::cout<<"序列化失败\n";
         return false;
    }
    dst = ss.str();
    return true;
}
bool Deserialize(std::string& src,Json::Value& val)//将序列化字符串反序列化为Json::Value
{
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    std::string err;
    if(!cr->parse(src.c_str(),src.c_str()+src.size(),&val,&err))
    {
        std::cout<<"反序列化失败\n";
        return false;
    }
    return true;
}

class ESIndex//创建es中的索引
{
public:
    ESIndex(std::shared_ptr<elasticlient::Client>& client,
    const std::string& name,const std::string& type = "_doc")
    :_client(client),_name(name),_type(type)//构建setting对象并添加到_index
    {
        Json::Value setting;
        Json::Value analysis;
        Json::Value analyzer;
        Json::Value ik;
        ik["tokenizer"] ="ik_max_word";
        analyzer["ik"] = ik;
        analysis["analyzer"] = analyzer;
        setting["analysis"] = analysis;
        _index["setting"] = setting;
    }
    ESIndex& append(const std::string& key,const std::string& type="text",
    const std::string& analyzer ="ik_max_word",bool enabled=true)//构建一个fields，并添加到_properties
    {
        Json::Value fields;
        fields["type"] = type;
        fields["analyzer"] = analyzer;
        if(!enabled) fields["enabled"] = false;
        _properties[key] = fields;
        return *this;
    }

    //完整的构建_index，并使用_index创建出来索引，添加到es中
    //且这里的index_id是用来在同一个索引名字下表示某一个索引的唯一性的(同一个索引名字可以创建多个索引)
    //但是同一个索引在es7.0后不能有多个类型了
    bool  create(const std::string& index_id = "default_index_id")
    {
        Json::Value mappings;
        mappings["dynamic"] = true;
        mappings["properties"] = _properties;
        _index["mappings"] =mappings;
        std::string body;
        if(!Serialize(_index,body))
        {
            LOG_ERROR("序列化索引失败!\n");
            return false;
        }
        LOG_DEBUG("序列化索引成功,索引为:\n{}",body);
        try
        {
            auto resp = _client->index(_name, _type,index_id,body);
            if(resp.status_code/100!=2)
            {
                LOG_ERROR("创建索引{}失败,响应错误码异常:{}\n",_name,resp.status_code);
                return false;
            }
        }
        catch(std::exception& e)
        {
            LOG_ERROR("创建索引{}失败,异常:{}\n",_name,e.what());
            return false;
        }
        return true;
    };
private:
    std::shared_ptr<elasticlient::Client>& _client;
    std::string _name;
    std::string _type;
    Json::Value _index;
    Json::Value _properties;
};
class ESInsert//对某一个索引添加文档
{
public:
    ESInsert(std::shared_ptr<elasticlient::Client>& client,
    const std::string& name,const std::string& type = "_doc")
    :_client(client),_name(name),_type(type)
    {};
    template<class T>
    ESInsert& append(const std::string& key,const T& val)
    {
        _item[key] = val;
        return *this;
    }
    bool insert(const std::string id="")//默认是空字符串，如果是空字符串，系统会自动生成一个
    {
        std::string body;
         if(!Serialize(_item,body))
        {
            LOG_ERROR("序列化文档失败!\n");
            return false;
        }
        LOG_DEBUG("序列化文档成功,文档为:\n{}",body);
        try
        {
            auto resp = _client->index(_name, _type,id,body);
            if(resp.status_code/100!=2)
            {
                LOG_ERROR("添加文档{}失败,响应错误码异常:{}\n",_name,resp.status_code);
                return false;
            }
        }
        catch(std::exception& e)
        {
            LOG_ERROR("添加文档{}失败,异常:{}\n",_name,e.what());
            return false;
        }
        return true;
    }
private:
    std::shared_ptr<elasticlient::Client>& _client;
    std::string _name;
    std::string _type;
    Json::Value _item;
};
class ESRomove//对一个索引或者文档的删除
{
public:
    ESRomove(std::shared_ptr<elasticlient::Client>& client,
    const std::string& name,const std::string& type = "_doc")
    :_client(client),_name(name),_type(type)
    {};
    bool remove(const std::string& id)
    {
        try
        {
            auto resp = _client->remove(_name, _type,id);
            if(resp.status_code/100!=2)
            {
                LOG_ERROR("删除{}失败,响应错误码异常:{}\n",_name,resp.status_code);
                return false;
            }
        }
        catch(std::exception& e)
        {
            LOG_ERROR("删除{}失败,异常:{}\n",_name,e.what());
            return false;
        }
        return true;
    }
private:
    std::shared_ptr<elasticlient::Client>& _client;
    std::string _name;
    std::string _type;
};


class ESSearch//查询设定条件下的索引和文档
{
public:
    ESSearch(std::shared_ptr<elasticlient::Client>& client,
    const std::string& name,const std::string& type = "_doc")
    :_client(client),_name(name),_type(type)
    {};
    //搜索时排除key中和val完全匹配的
    ESSearch& append_must_not_terms(const std::string& key,const std::vector<std::string>& vals)
    {
        Json::Value one_must_not;
        Json::Value terms;
        for(auto e:vals)
            terms[key].append(e);
        one_must_not["terms"] = terms;
        _must_not.append(one_must_not);
        return *this;
    };
    //搜索时搜索key中和val完全匹配的
    ESSearch& append_must_terms(const std::string& key,const std::string& val)
    {
        Json::Value one_must;
        Json::Value term;
        term[key] =val;
        one_must["term"] = term;
        _must.append(one_must);
        return *this;
    };
    //在key中存在和val部分匹配的就被搜索
    ESSearch& append_should_match(const std::string& key,const std::string& val)
    {
        Json::Value one_should;
        Json::Value match;
        match[key] =val;
        one_should["match"] = match;
        _should.append(one_should);
        return *this;
    };
    //搜索时搜索key中和val一定存在部分匹配的
    ESSearch& append_must_match(const std::string& key,const std::string& val)
    {
        Json::Value one_must;
        Json::Value match;
        match[key] =val;
        one_must["match"] = match;
        _must.append(one_must);
        return *this;
    };
    Json::Value search()
    {
        //1.构建查询请求
        Json::Value root;
        Json::Value query;
        Json::Value cond;
        if(!_must.empty()) cond["must"] = _must;
        if(!_must_not.empty()) cond["must_not"] = _must_not;
        if(!_should.empty()) cond["should"] = _should;
        query["bool"] =cond;
        root["query"] = query;

        //2.序列化查询请求并进行查询
        std::string body;
         if(!Serialize(root,body))
        {
            LOG_ERROR("序列化查询请求失败!\n");
            return Json::Value();
        }
        LOG_DEBUG("序列化查询请求成功,查询请求为:\n{}",body);

        cpr::Response resp;
        try
        {
            resp = _client->search(_name, _type,body);
            if(resp.status_code/100!=2)
            {
                LOG_ERROR("查询请求{}失败,响应错误码异常:{}\n",_name,resp.status_code);
                return Json::Value();
            }
        }
        catch(std::exception& e)
        {
            LOG_ERROR("查询请求{}失败,异常:{}\n",_name,e.what());
            return Json::Value();
        }
        LOG_DEBUG("检索响应正文:\n{}",resp.text);

        //3.反序列化查询结果，得到Json::Value类型的返回值
        Json::Value ret;
        if(!Deserialize(resp.text,ret))
        {
            LOG_ERROR("检索数据{}结果序列化失败\n",resp.text);
            return Json::Value();
        }
        return ret["hits"]["hits"];
    }

private:
    std::shared_ptr<elasticlient::Client>& _client;
    std::string _name;
    std::string _type;
    Json::Value _must;
    Json::Value _must_not;
    Json::Value _should;
};
}